import { Scene } from '@/utils/render/scene';
import { Sprite } from '@/utils/render/sprite';

interface TRaffleVEggMachineBoxConfig {
  imgEggMachineBg: string; // 蛋机背景图
  numberPadTop: number; // 盒子头部边距
  numberPadRight: number; // 盒子头部边距
  numberPadBottom: number; // 盒子底部边距
  numberPadLeft: number; // 盒子底部边距
  numberBoxPad: number; // 放蛋时, 盒子左右的不放蛋间距, 注:(初始化蛋蛋将不会落在边距数值之中, 但运动并不影响)
  numberBallSize: number; // 球的尺寸
  numberBallImgPadOfH: number; // 水平方向上, 球图片实物的左右边距(即图片如果存在左右空白，即此空白的像素)
  numberBallImgPadOfV: number; // 垂直方向上, 球图片实物的上下边距(即图片如果存在上下空白，即此空白的像素)
  numberSpeed: number; // 球运动的速度
  listBalls: string[]; // 蛋球图片列表
  booleanAnimationShow: boolean; // 是否演示蛋球动画
}

// 配置
const innerConfig: TRaffleVEggMachineBoxConfig = {
  imgEggMachineBg: require('@/assets/imgs/thomasBg.png'),
  numberPadTop: 30,
  numberPadRight: 30,
  numberPadBottom: 35,
  numberPadLeft: 30,
  numberBoxPad: 0,
  numberBallSize: 300 * 0.45,
  //! numberBallImgPadOfH: 此数值超过 23 则会导致 isOverBorder 重复调用，以至于栈堆溢出，目前不知道哪里出问题
  numberBallImgPadOfH: 14,
  numberBallImgPadOfV: 13,
  numberSpeed: 20,
  listBalls: [
    require('@/assets/imgs/egg1.png'),
    require('@/assets/imgs/egg2.png'),
    require('@/assets/imgs/egg3.png'),
    require('@/assets/imgs/egg2.png'),
    require('@/assets/imgs/egg1.png'),
    require('@/assets/imgs/egg3.png'),
    require('@/assets/imgs/egg2.png'),
    require('@/assets/imgs/egg3.png'),
    require('@/assets/imgs/egg1.png'),
    require('@/assets/imgs/egg2.png'),
  ],
  booleanAnimationShow: true,
}

interface IRenderListItem {
  texture: string;
  width: number;
  height: number;
  angle: number;
  pos: number[];
  anchor: number[];
  extra: any;
}

class EggMachine {
  private scene!: Scene; // 画布场景
  private spriteList: Sprite[] = []; // 元素列表
  private appScale = 1; // app的缩放比例
  private stop = false; // 停止动画

  public set = {
    speed: 0
  }

  constructor (
    private parent: Element,
    private canvas: HTMLCanvasElement,
    immediate: boolean = false
  ) {
    if (immediate) {
      this.initCanvas();
    }
  }

  // 球运动的速度
  private get speed () {
    return this.set.speed || innerConfig.numberSpeed;
  }

  // 加载资源
  private async loadSources () {
    const sources: any = {};
    if (innerConfig.imgEggMachineBg) {
      sources.imgEggMachineBg = innerConfig.imgEggMachineBg;
    }
    const balls = this.addGifts();
    await this.scene.load({
      ...sources,
      ...balls,
    });
  }

  // 添加蛋球图片链接
  private addGifts () {
    const sources: any = {};
    innerConfig.listBalls.forEach((item, index) => {
      sources[`ball_${index}`] = item;
    });
    return sources;
  }

  /** 转换尺寸比例 */
  private transScale(num: number) {
    return num / this.appScale;
  }

  /** 获取球的实际可见尺寸：图片大小减去空白像素 */
  private getRealBallSize() {
    let {
      numberBallSize,
      numberBallImgPadOfH,
      numberBallImgPadOfV,
    } = innerConfig;
    numberBallSize = this.transScale(numberBallSize);
    numberBallImgPadOfH = this.transScale(numberBallImgPadOfH);
    numberBallImgPadOfV = this.transScale(numberBallImgPadOfV);
    // 球的可见像素实体尺寸
    const realWidthBall = numberBallSize - numberBallImgPadOfH * 2;
    const realHeightBall = numberBallSize - numberBallImgPadOfV * 2;
    return {
      realWidthBall,
      realHeightBall
    };
  }

  /** 获取初始的旋转角度, 作为运动方向 */
  private getDirectionOfInit (maxOfRow: number, col: number, list: IRenderListItem[]): number {
    const center = Math.floor(maxOfRow / 2);
    let angle = 45;
    let ballAtRight = Math.floor(Math.random() * 91 + 90);
    ballAtRight = Math.max(Math.min(ballAtRight, 160), 110);
    let ballAtLeft = Math.floor(Math.random() * 90);
    ballAtLeft = Math.max(Math.min(ballAtLeft, 70), 20);
    if (col > center) {
      // 球在右边
      angle = ballAtRight;
    } else if (col < center) {
      // 球在左边
      angle = ballAtLeft;
    } else {
      const ls = [ballAtLeft, ballAtRight];
      const ridx = Math.floor(Math.random() * 2);
      angle = ls[ridx];
    }
    // 如果随机生成的角度已经存在, 则重新生成
    const angles = list.map(item => item.extra.angle);
    if (angles.includes(angle)) {
      return this.getDirectionOfInit(maxOfRow, col, list);
    }
    return angle;
  }

  // 初始化canvas
  async initCanvas () {
    this.scene = new Scene(this.parent, this.canvas);
    await this.loadSources();
    this.renderEggMachine();
    this.renderEggs();
  }

  /** 重新渲染 */
  private reRender() {
    this.spriteList = [];
    this.scene.clearChildren();
    this.scene.clean();
    this.renderEggMachine();
    this.renderEggs();
    requestAnimationFrame(() => {
      this.stop = false
    })
  }

  //* <- 渲染
  /** 渲染蛋机盒子 */
  private renderEggMachine () {
    // 不存在盒子图片,则不渲染
    if (!innerConfig.imgEggMachineBg) { return; }
    const eggMachine = new Sprite({
      texture: 'imgEggMachineBg',
      width: this.scene.width,
      height: this.scene.height
    });
    this.scene.addChild(eggMachine);
  }

  /** 渲染蛋蛋 */
  private renderEggs () {
    const { width, height } = this.scene;
    let {
      numberBallSize,
      numberPadBottom,
      numberBoxPad,
      numberPadLeft,
      numberPadRight,
    } = innerConfig;
    // 等比计算
    numberBallSize = this.transScale(numberBallSize);
    numberPadBottom = this.transScale(numberPadBottom);
    numberBoxPad = this.transScale(numberBoxPad);
    numberPadLeft = this.transScale(numberPadLeft);
    numberPadRight = this.transScale(numberPadRight);
    // 球的可见像素实体尺寸
    const { realWidthBall, realHeightBall } = this.getRealBallSize();
    // 计算在当前 canvas 中, 一行最多可以放几个
    const padLAR = numberPadLeft + numberPadRight; // 盒子左右间距
    let maxOfRow = Math.floor((width - (padLAR + numberBoxPad)) / realWidthBall);
    // 获取渲染属性
    const renderList: IRenderListItem[] = [];
    const list: string[] = JSON.parse(JSON.stringify(innerConfig.listBalls));
    // 遍历数据, 渲染蛋蛋
    list.forEach((item, index) => {
      const col = index % maxOfRow; // 所处的列
      const row = Math.ceil((index + 1) / maxOfRow); // 所处的行
      // console.log('行列', row, col);
      // 当前行的 ball 的最大数量, 由于最后一行, 可能没有达到最大数量, 需要居中
      let currentMaxOfRow = list.slice((row - 1) * maxOfRow).length;
      currentMaxOfRow = Math.min(currentMaxOfRow, maxOfRow);
      // 计算偏移量
      let offsetH = 0; // 水平偏移量
      let offsetV = 0; // 垂直偏移量
      // 不是最顶层 且 不是第一行
      if (currentMaxOfRow === maxOfRow && row > 1) {
        const center = Math.floor(currentMaxOfRow / 2);
        if (col < center) {
          offsetH = row * 15;
        } else if (col > center) {
          offsetH = row * -15;
        }
      }
      if (row > 1) {
        offsetV = Number((realHeightBall / 8).toFixed(2));
        offsetV = offsetV * row;
      }
      // 左边空白间距 = (画布宽 - 球的数量 * 球的宽) / 2
      const padLeft = (width - currentMaxOfRow * realWidthBall) / 2;
      // X轴上的定位 = 球所处的列 * 一个球可见像素(真实球)宽 + 真实球的一半(由于锚点为中心点, x 为0 有一半在视觉外) + 左空白间距
      let posX = (col * realWidthBall) + (realWidthBall / 2) + padLeft + offsetH;
      // y轴上的定位 = (画布高度 - 球高度的一半) - 底部边距 - 当前所处行的高 + 垂直偏移量
      let posY = (height - realWidthBall / 2) - numberPadBottom - ((row - 1) * realHeightBall) + offsetV;
      posX = Math.floor(posX);
      posY = Math.floor(posY);
      // 获取作为运动方向的角度
      const dire = this.getDirectionOfInit(currentMaxOfRow, col, renderList);
      // 保存生成 sprite 的属性
      renderList.push({
        texture: `ball_${index}`,
        width: numberBallSize,
        height: numberBallSize,
        angle: Math.floor(Math.random() * 360),
        pos: [posX, posY],
        anchor: [0.5, 0.5],
        extra: { col, row, angle: dire }
      });
    });
    // 元素的渲染层级需要反过来
    renderList.reverse();
    renderList.forEach((item) => {
      const egg = new Sprite(item);
      this.spriteList.push(egg);
      this.scene.addChild(egg);
    });
  }
  //* 渲染 ->

  // *<- 动画运动
  /** 根据角度, 计算位移坐标 */
  private calcPos (pos: number[], angle: number, stepMove?: number) {
    const frameMove = stepMove || 1; // 每帧移动距离
    // 三角函数计算, 正切公式: ∠A = 对边:邻边
    const sin = Math.sin(angle * (Math.PI / 180));
    const cos = Math.cos(angle * (Math.PI / 180));
    const [x1, y1] = pos;
    const lingBian = frameMove * cos;
    const duiBian = frameMove * sin;
    const x2 = x1 + lingBian;
    const y2 = y1 + duiBian;
    return [x2, y2];
  }

  /** 判断球运动的落点是否超出边界 */
  private isOverBorder(pos: number[]) {
    let {
      numberPadLeft,
      numberPadTop,
      numberPadRight,
      numberPadBottom,
    } = innerConfig;
    numberPadLeft = this.transScale(numberPadLeft);
    numberPadTop = this.transScale(numberPadTop);
    numberPadRight = this.transScale(numberPadRight);
    numberPadBottom = this.transScale(numberPadBottom);
    const { realWidthBall, realHeightBall } = this.getRealBallSize();
    const ballRadiusX = realWidthBall / 2; // 球体半径
    const ballRadiusY = realHeightBall / 2; // 球体半径
    const { width, height } = this.scene;
    const [x, y] = pos;
    // 根据条件判断是否越界
    const XMin = ballRadiusX + numberPadLeft;
    const YMin = ballRadiusY + numberPadTop;
    const XMax = width - ballRadiusX - numberPadRight;
    const yMax = height - ballRadiusY - numberPadBottom;
    if (
        x <= XMin ||
        y <= YMin ||
        x >= XMax ||
        y >= yMax
    ) {
      // console.log('pos:', pos, 'XMin:', XMin, 'XMax:', XMax, 'YMin:', YMin, 'yMax:', yMax)
      // throw new Error('球体越界');
      return true;
    }
    return false;
  }

  /** 获取反射角 */
  private getReflexAngle(angle: number) {
    let reflexAngle = angle + 270;
    const randomAngle = Math.floor(Math.random() * 17);
    reflexAngle += randomAngle;
    // 如果超过 360°，反向反射
    reflexAngle = reflexAngle > 360 ? reflexAngle - 360 : reflexAngle;
    return reflexAngle;
  }

  /**
   * 获取下一步的坐标
   * @param pos 当前的坐标
   * @param angle 当前的角度
   * @param rebound 碰到边界反弹的距离
   */
  private getNextStepPos(pos: number[], angle: number): {
    angle: number,
    pos: number[]
  } {
    const stepMove = this.transScale(this.speed);
    const nextPos = this.calcPos(pos, angle, stepMove);
    const isOver = this.isOverBorder(nextPos);
    // 超出边界, 递归重新计算
    if (isOver) {
      const reflexAngle = this.getReflexAngle(angle);
      return this.getNextStepPos(pos, reflexAngle);
    }
    return {
      angle,
      pos: nextPos
    };
  }

  /** 获取旋转角度 */
  private getRotateAngle(angle: number) {
    // 假设, 从最左边往最右边移动, 蛋蛋会旋转一周, 即 360度
    // 由此, 可计算出, 每走一步, 蛋蛋需要选择的度数
    // 蛋蛋的初始角度 + 渲染的角度, 超出360则减去360, 可得实际所处的角度
    const allStep = this.scene.width / this.transScale(this.speed);
    const eachAngle = Number((360 / allStep).toFixed(2));
    let nextAngle = angle + eachAngle;
    nextAngle = nextAngle > 360 ? nextAngle - 360 : nextAngle;
    return nextAngle;
  }

  /** 开始动画 */
  public startAnimation() {
    if (this.stop) { return }
    this.spriteList.forEach(item => {
      const angle = item.extra.angle;
      const nextStep = this.getNextStepPos(item.pos, angle);
      const nextAngle = this.getRotateAngle(item.angle);
      item.pos = nextStep.pos;
      item.angle = nextAngle;
      item.extra.angle = nextStep.angle;
    });
    this.scene.clean();
    this.scene.reRenderByCache();
    requestAnimationFrame(() => {
      this.startAnimation();
    });
  }

  /** 停止动画并重置 */
  public stopAnimation() {
    this.stop = true;
    this.reRender();
  }
  // * 动画运动 ->
}

export {
  EggMachine
}